#ifndef __ETLTRACE_H__
#define __ETLTRACE_H__

#ifndef __ETLBASE_H__
	#error etltrace.h requires etlbase.h to be included first
#endif

#ifndef IF_USE_SYZ_TRACE
#define IF_USE_SYZ_TRACE	1
#endif

#ifndef IF_TRACE_TIME
#define IF_TRACE_TIME		0
#endif

#ifndef IF_TRACE_CPU_TICK
#define IF_TRACE_CPU_TICK	0
#endif

#if IF_USE_SYZ_TRACE
#define SYZ_TRACE etlTrace
#define SYZ_TRACE0(s) SYZ_TRACE(_T(s))
#define SYZ_TRACE1(s, p1) SYZ_TRACE(_T(s), p1)
#define SYZ_TRACE2(s, p1, p2) SYZ_TRACE(_T(s), p1, p2)
#define SYZ_TRACE3(s, p1, p2, p3) SYZ_TRACE(_T(s), p1, p2, p3)
#define SYZ_TRACE4(s, p1, p2, p3, p4) SYZ_TRACE(_T(s), p1, p2, p3, p4)
#define SYZ_TRACE5(s, p1, p2, p3, p4, p5) SYZ_TRACE(_T(s), p1, p2, p3, p4, p5)
#define SYZ_TRACE6(s, p1, p2, p3, p4, p5, p6) SYZ_TRACE(_T(s), p1, p2, p3, p4, p5, p6)

#define SYZ_LOG0(s) etlLog(_T(__FILE__), __LINE__, s)
#define SYZ_LOG1(s, p1) etlLog(_T(__FILE__), __LINE__, s, p1)
#define SYZ_LOG2(s, p1, p2) etlLog(_T(__FILE__), __LINE__, s, p1, p2)
#define SYZ_LOG3(s, p1, p2, p3) etlLog(_T(__FILE__), __LINE__, s, p1, p2)
#define SYZ_LOG4(s, p1, p2, p3, p4) etlLog(_T(__FILE__), __LINE__, s, p1, p2, p3, p4)
#define SYZ_LOG5(s, p1, p2, p3, p4, p5) etlLog(_T(__FILE__), __LINE__, s, p1, p2, p3, p4, p5)

#define SYZ_FUNTRACE(strFunName) _TraceStack<false> __Fun(_T(strFunName))
#define SYZ_FUNTRACE_MOD(strFunName) _TraceStack<true> __Fun(_T(strFunName))

#define SYZ_TRACE_VALUE(exp)	do{\
									LPCTSTR lpszTypeFmt = typeToFmt(typeid(exp));\
									chString strTrace;\
									if(lpszTypeFmt == NULL && sizeof(exp) > 4)\
									{/*POD type*/\
										strTrace.Format(_T("[(%hs) %hs] = [(%d) %%s]"), typeid(exp).name(), #exp, sizeof(exp));\
										etlTrace(strTrace, (LPCTSTR)etlBin2Text((const BYTE*)&(exp), sizeof(exp)));\
									}\
									else if(lpszTypeFmt != NULL)\
									{/*block data*/\
										strTrace.Format(_T("[(%hs) %hs] = [%s]"), typeid(exp).name(), #exp, lpszTypeFmt);\
										etlTrace(strTrace, exp);\
									}\
									else \
									{/*normal point*/\
										strTrace.Format(_T("[(%hs) %hs] = [Addr 0x%%.8X]"), typeid(exp).name(), #exp);\
										etlTrace(strTrace, exp);\
									}\
								}while(0)
#else
#define SYZ_TRACE NULL
#define SYZ_TRACE0(s) NULL
#define SYZ_TRACE1(s, p1) NULL
#define SYZ_TRACE2(s, p1, p2) NULL
#define SYZ_TRACE3(s, p1, p2, p3) NULL
#define SYZ_TRACE4(s, p1, p2, p3, p4) NULL
#define SYZ_TRACE5(s, p1, p2, p3, p4, p5) NULL
#define SYZ_TRACE6(s, p1, p2, p3, p4, p5, p6) NULL

#define SYZ_LOG0(s) NULL
#define SYZ_LOG1(s, p) NULL
#define SYZ_LOG2(s, p1, p2) NULL
#define SYZ_LOG3(s, p1, p2, p3) NULL
#define SYZ_LOG4(s, p1, p2, p3, p4) NULL
#define SYZ_LOG5(s, p1, p2, p3, p4, p5) NULL

#define SYZ_FUNTRACE(strFunName) NULL
#define SYZ_FUNTRACE_MOD(strFunName) NULL
#endif

namespace ETL_TraceHelper
{
void __cdecl etlTrace(LPCTSTR lpszFormat, ...);
void __cdecl etlLog(LPCTSTR lpFile, int nLine, LPCTSTR lpszFormat, ...);

class CTraceHelper;
extern CTraceHelper _TraceHelper;
//////////////////////////////////////////////////////////////////////////
// LCProcessObject
template <class T_object>
class LCProcessObject
{
public:
	T_object& GetObject()
	{
		return *m_pCompositeData->m_pHeapData;
	}
public:

	LCProcessObject(LPCTSTR lpszMapFileType)
	{
//		chString strOut;
//		strOut.Format(_T("RED:: LCProcessObject::LCProcessObject() Mod:[%.8x] this:[%.8X:%.4X]\n"), ModuleFromAddress(this), this, GetCurrentThreadId());
//		OutputDebugString((LPCTSTR)strOut);

		chASSERT(m_pCompositeData == NULL && m_hFileHandle == NULL);
		TCHAR sMapFileName[MAX_PATH] = _T("");
		_stprintf(sMapFileName, _T("Object_System_%s_%x"), lpszMapFileType, GetCurrentProcessId()); 
		m_hFileHandle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
			sizeof _TCompositeData, sMapFileName);

		chASSERT(m_hFileHandle != NULL);

		if(m_hFileHandle != NULL)
			m_pCompositeData = (_TCompositeData*)::MapViewOfFile(m_hFileHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);

		chASSERT(m_pCompositeData != NULL);
		if(m_pCompositeData != NULL && m_pCompositeData->m_pWhoAlloc == NULL)
		{
//			OutputDebugString(_T("RED:: new ProcessObject\n"));
//			new(m_pCompositeData) _TCompositeData;
			m_pCompositeData->m_pWhoAlloc = this;
			m_pCompositeData->m_pHeapData = new T_object;
		}
	}

	~LCProcessObject()
	{
//		chString strOut;
//		strOut.Format(_T("RED:: LCProcessObject::~LCProcessObject() Mod:[%.8x] this:[%.8X:%.4X]\n"),
//			ModuleFromAddress(this), this, GetCurrentThreadId());
//		OutputDebugString((LPCTSTR)strOut);

		chASSERT(m_pCompositeData != NULL);
		if(m_pCompositeData->m_pWhoAlloc == this) // who malloc who free
		{
//			OutputDebugString(_T("RED:: delete ProcessObject\n"));
//			m_pCompositeData->~_TCompositeData();
			m_pCompositeData->m_pWhoAlloc = NULL;
			delete m_pCompositeData->m_pHeapData;
		}
		
		UnmapViewOfFile(m_pCompositeData);
		m_pCompositeData = NULL;

		CloseHandle(m_hFileHandle);
		m_hFileHandle = NULL;
	}
private:
	HANDLE m_hFileHandle;
	struct _TCompositeData
	{
	public:
		LCProcessObject* m_pWhoAlloc;
		T_object* m_pHeapData;
	}*m_pCompositeData;
};

template <bool bTraceMod>
PRIVATE_CLASS _TraceStack
{
	LPCTSTR m_strFunName;
public:
	_TraceStack(LPCTSTR strName)
	{
		m_strFunName = strName;
		
		LONG& level = _TraceHelper.GetStackLevel();

		InterlockedIncrement(&level);
		chString tStr(' ', level * 2);
		if(bTraceMod)
			etlTrace(_T("%.8X %s{  %s"), _Module.GetModuleInstance(), (LPCTSTR)tStr, m_strFunName);
		else
			etlTrace(_T("%s{  %s"), (LPCTSTR)tStr, m_strFunName);
	}
	~_TraceStack()
	{
		LONG& level = _TraceHelper.GetStackLevel();
		
		chString tStr(' ', level * 2);
		if(bTraceMod)
			etlTrace(_T("%.8X %s}  %s"), _Module.GetModuleInstance(), (LPCTSTR)tStr, m_strFunName);
		else
			etlTrace(_T("%s}  %s"), (LPCTSTR)tStr, m_strFunName);
		InterlockedDecrement(&level);
		if(level == 0)
			_TraceHelper.RemoveStackLevel();
	}
};

typedef chObjMap_FixedArray<DWORD, LONG, 256> mapThreadDeepth;
typedef LCProcessObject< mapThreadDeepth > uniProcessObject;

class CTraceHelper : public uniProcessObject
{
public:
	BOOL Init()
	{
		return TRUE;
	}
	void Term()
	{	
	}
	
	LONG& GetStackLevel()
	{
		DWORD dwThreadID = GetCurrentThreadId();
		mapThreadDeepth& rThreadDeepth = uniProcessObject::GetObject();
		return rThreadDeepth[dwThreadID];
	}
	void RemoveStackLevel()
	{
		DWORD dwThreadID = GetCurrentThreadId();
		mapThreadDeepth& rThreadDeepth = uniProcessObject::GetObject();
		rThreadDeepth.erase(dwThreadID);
	}
	BOOL IsTraceEnable()
	{
		return m_bTraceEnable;
	}
private:
	BOOL GetTraceConfig()
	{
		HKEY hKey = 0;
		if(ERROR_SUCCESS != RegOpenKeyEx(HKEY_CURRENT_USER, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion"), 0, KEY_READ, &hKey))
			return FALSE;

		DWORD nType = REG_DWORD;
		DWORD dwSize = 4;
		DWORD dwEnable = 0;

		if(ERROR_SUCCESS == RegQueryValueEx(hKey, _T("SysLog"), NULL, &nType, (BYTE*)&dwEnable, &dwSize))
			m_bTraceEnable = dwEnable != 0;

		RegCloseKey(hKey);
		return TRUE;
	}
public:
	CTraceHelper(LPCTSTR lpszTipTitle = NULL)
		: uniProcessObject(_T("stackDeepth"))
	{
		SYZ_TRACE2("Last build at %s %s", _T(__DATE__), _T(__TIME__));
		GetTraceConfig();
	}
	~CTraceHelper()
	{
	}
private:
	BOOL m_bTraceEnable;
};

#ifdef _DEBUG
#define IF_TRACE_ENABLE		TRUE
#define IF_LOGFILE_ENABLE		FALSE
#else
#define IF_TRACE_ENABLE			_TraceHelper.IsTraceEnable()
#define IF_LOGFILE_ENABLE		_TraceHelper.IsTraceEnable()
#endif

inline void __cdecl etlTrace(LPCTSTR lpszFormat, ...)
{
	chASSERT(lpszFormat != NULL);

	if(!IF_TRACE_ENABLE)
		return;

	va_list argList;
	va_start(argList, lpszFormat);
	chString strText;
	strText.FormatV(lpszFormat, argList);
	va_end(argList);
	
	SYSTEMTIME SysTime;
	etlGetLocalTime(&SysTime);
	chString strOut;

#ifndef USER_NAME
#define USER_NAME _T("NoName")
#endif

#if IF_TRACE_TIME
	#if IF_TRACE_CPU_TICK
		strOut.Format(_T("[%s+%.4x:%.4x] %.2d:%.2d:%.2d.%.3d %.12I64u %8.3f %s\n")
			, USER_NAME, GetCurrentProcessId(), GetCurrentThreadId()
			, SysTime.wHour, SysTime.wMinute, SysTime.wSecond, SysTime.wMilliseconds
			, GetCPUTickCount(), uCTimer::GetTickCount()
			, (LPCTSTR)strText);
	#else
		strOut.Format(_T("[%s+%.4x:%.4x] %.2d:%.2d:%.2d.%.3d %8.3f %s\n")
			, USER_NAME, GetCurrentProcessId(), GetCurrentThreadId()
			, SysTime.wHour, SysTime.wMinute, SysTime.wSecond, SysTime.wMilliseconds, uCTimer::GetTickCount()
			, (LPCTSTR)strText);
	#endif
#else
	#if IF_TRACE_CPU_TICK
		strOut.Format(_T("[%s+%.4x:%.4x] %.12I64u %8.3f %s\n")
			, USER_NAME, GetCurrentProcessId(), GetCurrentThreadId()
			, GetCPUTickCount(), uCTimer::GetTickCount()
			, (LPCTSTR)strText);
	#else
		strOut.Format(_T("[%s+%.4x:%.4x] %8.3f %s\n")
			, USER_NAME, GetCurrentProcessId(), GetCurrentThreadId(), uCTimer::GetTickCount()
			, (LPCTSTR)strText);
	#endif
#endif

	OutputDebugString((LPCTSTR)strOut);
}

inline void __cdecl etlLog(LPCTSTR lpFile, int nLine, LPCTSTR lpszFormat, ...)
{
	chASSERT(lpszFormat != NULL);
	TCHAR szFileName[MAX_PATH] = _T("");
	chStrcpy<TCHAR>(szFileName, lpFile);
	etlPathStripPath(szFileName);
	
	va_list argList;
	va_start(argList, lpszFormat);
	chString strText;
	strText.FormatV(lpszFormat, argList);
	va_end(argList);
	
	SYSTEMTIME SysTime;
	GetLocalTime(&SysTime);
	chString strOut;
	strOut.Format(_T("[%.4x:%.4x] %.2d:%.2d:%.2d.%.3d %8.3f [%s:%d] %s\r\n"), GetCurrentProcessId(), GetCurrentThreadId()
		, SysTime.wHour, SysTime.wMinute, SysTime.wSecond, SysTime.wMilliseconds, uCTimer::GetTickCount()
		, szFileName, nLine, (LPCTSTR)strText);
	
	if(IF_LOGFILE_ENABLE)
	{
		TCHAR LogFileName[64];
		_stprintf(LogFileName, _T(".\\%.4d-%.2d-%.2d %.2d-%.2d.txt"), SysTime.wYear, SysTime.wMonth, SysTime.wDay, SysTime.wHour, SysTime.wMinute);
		BOOL bNew = !PathFileExists(LogFileName);
		FILE* pFile = _tfopen(LogFileName, _T("ab"));
		if(pFile != NULL)
		{
#ifdef _UNICODE
			if(bNew)
				fwrite("\xFF\xFE", 1, 2, pFile);
#endif
			fwrite((LPCTSTR)strOut, sizeof(TCHAR), strOut.GetLength(), pFile);
			fclose(pFile);
		}
		OutputDebugString((LPCTSTR)strOut);
	}
}

};
using namespace ETL_TraceHelper;
#endif //__ETLTRACE_H__

//class TClass
//{
//public:
//	TClass()
//	{
//		SYZ_TRACE(_T("TClass() %.8x, PID = %x"), this, GetCurrentProcessId());
//	}
//	~TClass()
//	{
//		SYZ_TRACE(_T("~TClass() %.8x, PID = %x"), this, GetCurrentProcessId());
//	}
//	TClass(TClass& rVal)
//	{
//		SYZ_TRACE(_T("TClass(TClass& rVal) %.8x, PID = %x"), this, GetCurrentProcessId());
//	}
//	TClass& operator = (TClass& rVal)
//	{
//		SYZ_TRACE(_T("TClass::=(TClass&) %.8x<=%.8x, PID = %x\n"), this, &rVal, GetCurrentProcessId());
//		return *this;
//	}
//	void* operator new(size_t, void* pData)
//	{
//		SYZ_TRACE(_T("TClass::new(size_t, void* pData) %.8x, PID = %x\n"), pData, GetCurrentProcessId());
//		return pData;			
//	}
//	void Dump()
//	{
//		SYZ_TRACE(_T("TClass::Dump() %.8x, PID = %x\n"), this, GetCurrentProcessId());
//	}
//};
